*&---------------------------------------------------------------------*
*& Report ZTEST01
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
report ztest01.

tables faglflext.

data gr_alv_table type ref to cl_salv_table.

types: begin of gty_list,
         rbukrs  type faglflext-rbukrs, "公司代码
         saknr   type faglflext-racct,  "科目号
         txt20   type skat-txt20,       "科目名称
         kostl   type faglflext-rcntr,  "成本中心
         ltext   type cskt-ltext,       "成本中心描述
         hsl01   type faglflext-hsl01,  "1月
         hsl02   type faglflext-hsl02,  "2月
         hsl03   type faglflext-hsl03,  "3月
         hsl04   type faglflext-hsl04,  "4月
         hsl05   type faglflext-hsl05,  "5月
         hsl06   type faglflext-hsl06,  "6月
         hsl07   type faglflext-hsl07,  "7月
         hsl08   type faglflext-hsl08,  "8月
         hsl09   type faglflext-hsl09,  "9月
         hsl10   type faglflext-hsl10,  "10月
         hsl11   type faglflext-hsl11,  "11月
         hsl12   type faglflext-hsl12,  "12月
         hsl_sum type faglflext-hsl12,  "合计
       end of gty_list.

* 数据清单
data gt_list type standard table of gty_list.
data gs_list like line of gt_list.

* 用于数据输出
data: begin of gs_output,
        rbukrs  type faglflext-rbukrs, "公司代码
        saknr   type faglflext-racct,  "科目号
        txt20   type skat-txt20,       "科目名称
        hsl_sum type faglflext-hsl12,  "合计
      end of gs_output.
data gt_output like standard table of gs_output.

types: begin of ty_cskt,
         kostl type cskt-kostl,  "成本中心
         ktext type cskt-ktext,  "成本中心名称
       end of ty_cskt.
data: gs_cskt type ty_cskt,
      gt_cskt type standard table of ty_cskt.

data gc_dyn_prefix type string value 'HSL_RCNTR_'.

data gt_component type cl_abap_structdescr=>component_table.
data gs_component like line of  gt_component.

* 用于数据输出，指向gt_output
field-symbols: <dyn_table> type standard table,
               <dyn_wa>    type any.


*--------------------------------------------------------------
* 选择屏幕
*--------------------------------------------------------------
select-options s_bukrs for faglflext-rbukrs memory id buk no-extension no intervals obligatory. "company code
parameters p_ryear type faglflext-ryear default sy-datum+0(4) obligatory. "year
parameters p_rpmax type bseg-h_monat obligatory.                          "month


start-of-selection.
  perform frm_get_data.
  perform frm_add_dyn_fields.
  perform frm_process_data.

end-of-SELECTION.
  perform frm_alv_show tables <dyn_table>.



*&---------------------------------------------------------------------*
*& Form frm_get_data
*&---------------------------------------------------------------------*
form frm_get_data .
  ranges: lr_racct for faglflext-racct.

  lr_racct[] = value #( ( sign = 'I' option = 'CP' low = '0080*' ) ).

* 从FAGLFLEXT获取数据
  select rbukrs             "公司代码
        , racct as saknr    "科目号
        , rcntr as kostl    "成本中心
        , hsl01             "1月
        , hsl02             "2月
        , hsl03
        , hsl04
        , hsl05
        , hsl06
        , hsl07
        , hsl08
        , hsl09
        , hsl10
        , hsl11
        , hsl12
     from v_faglflext_view
     where rbukrs in @s_bukrs
     and ryear eq @p_ryear
     and rldnr eq '0L'
     and rrcty eq '0'
     and rvers eq '001'
     and racct in @lr_racct
     into table @data(lt_faglflext).

* 按照gs_list格式对数据进行合计
  loop at lt_faglflext into data(ls_faglflext).
    move-corresponding ls_faglflext to gs_list.
    collect gs_list into gt_list.

    clear: ls_faglflext, gs_list.
  endloop.

  " 获取会计科目描述
  select distinct saknr from @lt_faglflext as t into table @data(lt_saknr) .
  select s~saknr,
         s~txt20
      from skat as s
      inner join @lt_saknr as t on s~saknr eq t~saknr
      where s~ktopl eq '1000'and s~spras eq @sy-langu
      into table @data(lt_skat).
  sort lt_skat by saknr.

  " 获取成本中心描述
  select distinct kostl from @lt_faglflext as t into table @data(lt_cntr). " cost center
  select c~kostl, c~ktext
      from cskt as c
      inner join @lt_cntr as  d  on c~kostl eq d~kostl
      where c~kokrs eq '1000' and c~spras eq @sy-langu and c~datbi ge @sy-datum
      into table @gt_cskt.
  sort gt_cskt by kostl.


  " 将科目描述和成本中心描述填充到gt_list
  loop at gt_list into gs_list.
    if line_exists( lt_skat[ saknr = gs_list-saknr ] ).
      gs_list-txt20 = lt_skat[ saknr = gs_list-saknr ]-txt20.
    endif.

    if line_exists( gt_cskt[ kostl = gs_list-kostl ] ).
        gs_list-ltext = gt_cskt[ kostl = gs_list-kostl ]-ktext.
    endif.

    modify gt_list from gs_list.

    clear gs_list.
  endloop.
endform.


form frm_process_data .
  data lv_src_fldname type fieldname.
  data lv_target_fldname type fieldname.
  data gv_tabix like sy-tabix.

  lv_src_fldname = 'HSL' && p_rpmax. " 基于选择屏幕的月份
  condense lv_src_fldname.

*-------------------------------------------------------------
* 行转列，同时进行聚合计算
*-------------------------------------------------------------
  loop at gt_list into gs_list.
    move-corresponding gs_list to <dyn_wa>.

    " 从1到12月份，获取选择屏幕所在月份的数据
    assign component lv_src_fldname of structure gs_list to field-symbol(<lfs_source>).

    " 找到对应成本中心的index
    read table gt_cskt transporting no fields with key kostl = gs_list-kostl binary search.
    if sy-subrc = 0.
      gv_tabix = sy-tabix.
      lv_target_fldname = gc_dyn_prefix && gv_tabix.
      condense lv_target_fldname.  " 对应的动态列名

      " 找到成本中心对应的字段
      assign component lv_target_fldname of structure <dyn_wa> to field-symbol(<lfs_target>).
      <lfs_target> = <lfs_source>.

      " 合计字段
      assign component 'HSL_SUM' of structure <dyn_wa> to field-symbol(<lfs_sum>).
      <lfs_sum> = <lfs_source>.
    endif.

    collect <dyn_wa> into <dyn_table>.
    clear:  gs_list, <dyn_wa>.
  endloop.

*-----------------------------------------------------
* 删除空行
*-----------------------------------------------------
  data lt_component like gt_component.
  lt_component[] = gt_component[].

  " 删除不相关行
  delete lt_component where name = 'RBUKRS'.
  delete lt_component where name = 'SAKNR'.
  delete lt_component where name = 'TXT20'.


  loop at <dyn_table> assigning <dyn_wa>.
    data(lv_is_empty) = abap_true.

    " lt_component保存了所有gt_output的字段，删除了RBUKRS, SAKNR和TXT20
    loop at lt_component assigning field-symbol(<ls_comp>).
      assign component <ls_comp>-name of structure <dyn_wa> to field-symbol(<ls_field>).
      if <ls_field> is not initial.
        lv_is_empty = abap_false.
        exit.
      endif.
    endloop.

    if lv_is_empty = abap_true.
      delete <dyn_table>.
    endif.
  endloop.

endform.


*--------------------------------------------------------
* 基于有数据发生的成本中心，将成本中心加到输出字段中
*--------------------------------------------------------
form frm_add_dyn_fields .
data: ls_dref type ref to data,
      lt_dref type ref to data.

  data: lo_strcut_descr type ref to cl_abap_structdescr,
        lo_table_descr  type ref to cl_abap_tabledescr.

  data: lv_index     type string,
        lv_fieldname type string.
  data lv_reffield type faglflext-hsl01.

  free: gt_component.

* 基于gs_output
  lo_strcut_descr ?= cl_abap_structdescr=>describe_by_data( gs_output ).
  gt_component = lo_strcut_descr->get_components( ).

* 根据成本中心列表增加动态字段
  loop at gt_cskt into gs_cskt.
    lv_index = sy-tabix.

    lv_fieldname = gc_dyn_prefix && lv_index .
    condense lv_fieldname no-gaps.

    clear gs_component.
    gs_component-name = lv_fieldname.
    gs_component-type = cast #( cl_abap_elemdescr=>describe_by_data( lv_reffield ) ).
    append gs_component to gt_component.
  endloop.


  "根据字段目录创建动态结构类型
  lo_strcut_descr ?= cl_abap_structdescr=>create( p_components = gt_component ).
  lo_table_descr ?= cl_abap_tabledescr=>create( p_line_type = lo_strcut_descr ).


  create data lt_dref type handle lo_table_descr.
  create data ls_dref type handle lo_strcut_descr.
  assign ls_dref->* to <dyn_wa>.
  assign lt_dref->* to <dyn_table>.
endform.


form frm_alv_show  tables   t_data type standard table.
  perform frm_alv_set_style tables t_data[].
  gr_alv_table->display( ).
endform.


form frm_alv_set_style tables t_data type standard table.
  data ls_program type salv_s_layout_key."结构包含布局变式所属程序名
  data lv_tabix like sy-tabix.
  data lv_fieldname type fieldname.

  ls_program-report = sy-repid.

* 实例化SALV
  cl_salv_table=>factory(
    importing r_salv_table = gr_alv_table
    changing t_table = t_data[] ).


* 设置动态字段的field catalog
  data(lr_alv_columns) = gr_alv_table->get_columns( ).
  loop at gt_cskt into gs_cskt.
    lv_tabix = sy-tabix.

    lv_fieldname = gc_dyn_prefix && lv_tabix.
    condense lv_fieldname no-gaps.
    perform frm_set_field using lr_alv_columns lv_fieldname gs_cskt-ktext '' ''.

    clear gs_cskt.
  endloop.

* 设置合计字段的显示
  perform frm_set_field using lr_alv_columns 'HSL_SUM' '金额合计' '' ''.

* 优化列宽
  lr_alv_columns->set_optimize( 'X' ).


* 设置 toolbar
  gr_alv_table->get_functions( )->set_all(  ).


* 设置ALV布局
  data(lr_layout) = gr_alv_table->get_layout( ).
  lr_layout->set_key( ls_program ).                                "设置布局保存为变式时基于此Key
  lr_layout->set_save_restriction( cl_salv_layout=>restrict_none )."允许保存布局为变式

* 设置条纹显示
  gr_alv_table->get_display_settings(  )->set_striped_pattern( abap_true ).

endform.


form frm_set_field  using uo_cols type ref to cl_salv_columns_table
                    value(uv_fieldname)     " 字段名
                    value(uv_text)          " 字段文本
                    value(uv_length)        " 输出宽度
                    value(uv_istechnical).  " 是否隐藏

  data: lo_col    type ref to cl_salv_column_table,
        lo_agg    type ref to cl_salv_aggregations,
        lv_text_s type scrtext_s,
        lv_text_m type scrtext_m,
        lv_text_l type scrtext_l.

  lo_col ?= uo_cols->get_column( uv_fieldname ).

* 是否隐藏
  if not uv_istechnical is initial.
    lo_col->set_technical( abap_true ). "隐藏
  endif.

* 设置显示字段文本
  if not uv_text is initial.
    lv_text_s = uv_text.
    lv_text_m = uv_text.
    lv_text_l = uv_text.
    lo_col->set_short_text( lv_text_s ).
    lo_col->set_medium_text( lv_text_m ).
    lo_col->set_long_text( lv_text_l ).
  endif.

* 设置输出宽度
  if not uv_length is initial.
    lo_col->set_output_length( uv_length ).
  endif.

  lo_col->set_zero(  '' ).
endform.